import 'dart:math';

import 'package:flutter/material.dart';

import '../../../utils/ex/kq_ex.dart';
import '../base/base_chart.dart';
import '../ex/extension.dart';
import 'base/base_pie_entity.dart';
import 'base/base_pie_chart.dart';
import 'kq_pie_chart.dart';

/// 3D饼状图
class KqPieChart3DDelegate extends BasePieChartDelegate<List<PieChartEntity>> {
  /// 构建饼状图
  ///
  /// [innerRadius]为内圆半径，此值>0则绘制图形为环形，=0则为扇形。[sliceSpace]为各扇形直接的间隙。
  /// [baseMapColor]为饼状图背景颜色，如果数据为空的且未设置[emptyWidgetBuilder],则会在界面看到一个
  /// 以[baseMapColor]为背景颜色的圆(innerRadius == 0)或圆环(innerRadius>0)。[drawStartAngle]为
  /// 饼状图起始绘制角度。[labelPosition]指定饼状图标签绘制的位置。[emptyWidgetBuilder]为无数据时构建
  /// 需要显示的内容。
  KqPieChart3DDelegate(
      {super.animDuration,
      required super.radius,
      super.data,
      super.innerRadius,
      super.sliceSpace,
      super.baseMapColor,
      super.drawStartAngle,
      super.labelPosition,
      PieChart3DGestureHandler? gestureHandler,
      PieChart3DDataRender? dataRender,
      PieChart3DLabelRender? labelRender,
      super.emptyWidgetBuilder,
      super.isDataEmpty,
      this.flipAngle = 30,
      this.height = 20})
      : assert(flipAngle > 0 && flipAngle < 90,
            "flipAngle range is 0~90,current:$flipAngle"),
        super(
            dataRender: dataRender ?? const PieChart3DDataRender(),
            labelRender: labelRender,
            gestureHandler: gestureHandler);

  /// 构建饼状图，部分非必传属性如果未设置则使用默认值
  ///
  /// [innerRadius]为内圆半径，此值>0则绘制图形为环形，=0则为扇形。[sliceSpace]为各扇形直接的间隙。
  /// [baseMapColor]为饼状图背景颜色，如果数据为空的且未设置[emptyWidgetBuilder],则会在界面看到一个
  /// 以[baseMapColor]为背景颜色的圆(innerRadius == 0)或圆环(innerRadius>0)。[drawStartAngle]为
  /// 饼状图起始绘制角度。[labelPosition]指定饼状图标签绘制的位置。[emptyWidgetBuilder]为无数据时构建
  /// 需要显示的内容。
  KqPieChart3DDelegate.withDefault(
      {Duration? animDuration,
      required double radius,
      List<PieChartEntity>? data,
      double innerRadius = 0,
      double sliceSpace = 0,
      Color baseMapColor = Colors.transparent,
      double drawStartAngle = 0,
      LabelPosition? labelPosition,
      PieChart3DGestureHandler? gestureHandler,
      PieChart3DDataRender? dataRender,
      PieChart3DLabelRender? labelRender,
      Widget Function()? emptyWidgetBuilder,
      bool Function(List<PieChartEntity>? data)? isDataEmpty,
      double height = 20,
      double flipAngle = 30})
      : this(
            animDuration: animDuration,
            radius: radius,
            data: data,
            innerRadius: innerRadius,
            sliceSpace: sliceSpace,
            baseMapColor: baseMapColor,
            drawStartAngle: drawStartAngle,
            labelPosition: labelPosition,
            dataRender: dataRender,
            gestureHandler: gestureHandler ?? PieChart3DGestureHandler(),
            labelRender: labelRender ?? const PieChart3DLabelRender(),
            height: height,
            flipAngle: flipAngle,
            isDataEmpty: isDataEmpty,
            emptyWidgetBuilder: emptyWidgetBuilder ?? getDefEmptyView);

  /// 3D饼状图翻转角度，0°则和二维饼状图一致，90°则只能看到一个二维矩形.允许取值范围为0°~90°
  final double flipAngle;

  /// 3D饼状图高度,此高度为[flipAngle]为90°状态下的高度
  final double height;

  @override
  bool get isEmptyData {
    var isDataEmptyFunc = isDataEmpty;
    if (isDataEmptyFunc != null) {
      return isDataEmptyFunc.call(data);
    }

    return data.isNullOrEmpty;
  }

  /// 实际绘制时的翻转角度
  double get drawFlipAngle =>
      flipAngle +
      (gestureHandler == null
          ? 0
          : (gestureHandler as PieChart3DGestureHandler).flipAngle);

  /// 实际绘制时饼状图高度
  double get drawHeight => height * sin(drawFlipAngle.toRadian);

  @override
  void didUpdateWidget(covariant BaseChart oldWidget) {
    super.didUpdateWidget(oldWidget);
    _updateGestureHandler(oldWidget);
  }

  void _updateGestureHandler(BaseChart oldWidget) {
    var gestureHandler = this.gestureHandler;
    if (gestureHandler == null || gestureHandler is! PieChart3DGestureHandler) {
      return;
    }

    var oldGestureHandler = oldWidget.delegate.gestureHandler;
    if (oldGestureHandler == null ||
        oldGestureHandler is! PieChart3DGestureHandler) {
      return;
    }

    gestureHandler._rotation = oldGestureHandler._rotation;
    gestureHandler._firstMoveAngle = oldGestureHandler._firstMoveAngle;
    gestureHandler._touchData = oldGestureHandler._touchData;
    gestureHandler._oldTouchData = oldGestureHandler._oldTouchData;
    gestureHandler._rotationDirection = oldGestureHandler._rotationDirection;
    gestureHandler._flipAngle = oldGestureHandler._flipAngle;
    gestureHandler._isFlip = oldGestureHandler._isFlip;
    gestureHandler._isMove = oldGestureHandler._isMove;
  }
}

/// 绘制饼状图扇形内容
class PieChart3DDataRender with BasePieDataRender<KqPieChart3DDelegate> {
  const PieChart3DDataRender({this.highLightRender});

  final PieChart3DHighLightRender? highLightRender;

  @override
  void onDraw(KqPieChart3DDelegate chart, Canvas canvas, double animProgress) {
    if (chart.isEmptyData) {
      drawBg(chart, canvas);
      return;
    }

    PieChart3DGestureHandler? gestureHandler = chart.gestureHandler == null
        ? null
        : chart.gestureHandler as PieChart3DGestureHandler;

    // 记录超过240°的实体，超过240°的扇形侧边存在被下一个扇形侧边遮挡的问题，因此需要特殊处理
    PieChartEntity? over240Entity;
    double rotationAngle =
        chart.drawStartAngle + (gestureHandler?.rotation ?? 0);
    // 偏移角度，也是当前需要绘制扇形的起始角度
    double offsetDegrees = rotationAngle;
    PieChartEntity? touchData = gestureHandler?.touchData;
    PieChart3DHighLightRender? highLightRender = this.highLightRender;

    double totalValue = 0;
    var dataList = <PieChartEntity>[];
    if (chart.data != null) {
      // 不能直接使用原始数据，下方会对数据进行重新排序，改变原始数据会导致绘制疯狂闪
      dataList.addAll(chart.data!);
    }

    for (var element in dataList) {
      totalValue += element.value;
    }

    for (int i = 0; i < dataList.length; i++) {
      var data = dataList[i];
      if (!data.isDraw) {
        continue;
      }

      double degrees =
          totalValue == 0 ? 0 : data.value / totalValue * 360 * animProgress;

      data.$drawStartAngle = offsetDegrees;
      data.$drawSweepAngle = degrees;
      if (data.drawSweepAngle >= 240) {
        over240Entity = data;
      }

      if (touchData == data && highLightRender != null) {
        data.$drawRadius = highLightRender.getRadius(chart, data);
        Offset offset = highLightRender.getOffset(data);
        data.$offset = offset;
      } else {
        data.$drawRadius = chart.radius + data.addedRadius;
        data.$offset = Offset.zero;
      }

      offsetDegrees += degrees;
    }

    // 3D饼状图需要分四次绘制，分上下两部分，这样分批绘制主要是解决扇形直接相互遮挡问题
    // 第一部分，第一步绘制上半部分外圆半径阴影，第二步绘制底部扇形及开始和结束位置的矩形侧面
    // 第二部分，第一步绘制下班部分外圆半径阴影，第二步绘制顶部扇形

    // 这一步排序很重要，否则绘制会出现扇形直接被遮挡问题。
    // 此排序用于绘制第一部分
    dataList.sort(
      (o1, o2) {
        double o1Top =
            sin(getMinYAngle(o1.drawStartAngle, o1.drawSweepAngle).toRadian);
        double o2Top =
            sin(getMinYAngle(o2.drawStartAngle, o2.drawSweepAngle).toRadian);

        if (o1Top > o2Top) {
          return 1;
        } else if (o1Top == o2Top) {
          return 0;
        } else {
          return -1;
        }
      },
    );

    double sliceSpace = dataList.length == 1 ? 0 : chart.sliceSpace;
    double flipAngle = chart.drawFlipAngle;
    double height = chart.drawHeight;
    double centerX = chart.size.centerX;
    double centerY = chart.size.centerY;

    for (int j = 0; j < 2; j++) {
      for (int i = 0; i < dataList.length; i++) {
        var data = dataList[i];
        if (!data.isDraw || data.drawSweepAngle == 0) {
          continue;
        }

        if (data == touchData && highLightRender != null) {
          highLightRender.onDraw3D(chart, canvas, data.drawStartAngle,
              data.drawSweepAngle, data, false, j == 1, j == 0, false);
          continue;
        }

        if (j == 0) {
          canvas.draw3DArcShadow(
              center: Offset(centerX, centerY + height),
              radius: data.drawRadius,
              height: height,
              flipAngle: flipAngle,
              startAngle: data.drawStartAngle,
              sweepAngle: data.drawSweepAngle,
              color: data.color,
              offset: data.offset,
              sliceSpace: sliceSpace,
              drawTop: true);
        } else {
          canvas.draw3DArcBottom(
              center: Offset(centerX, centerY + height),
              radius: data.drawRadius,
              innerRadius: chart.innerRadius,
              height: height,
              flipAngle: flipAngle,
              startAngle: data.drawStartAngle,
              sweepAngle: data.drawSweepAngle,
              color: data.color,
              offset: data.offset,
              sliceSpace: sliceSpace);

          if (i > 0 && over240Entity != null) {
            double endAngle =
                (over240Entity.drawStartAngle + over240Entity.drawSweepAngle)
                    .to360Range;
            if (endAngle > 180 && endAngle < 240) {
              var originalIndex = dataList.indexOf(over240Entity);
              var originalIndex2 = dataList.indexOf(data);
              if (originalIndex2 == originalIndex + 1 &&
                  dataList.indexOf(over240Entity) < i) {
                // 超过240°的扇形，结束位置侧边存在被下一个扇形遮挡问题，因此在绘制下一个扇形前，重新绘制结束位置往前10°区域的扇形
                if (over240Entity == touchData && highLightRender != null) {
                  highLightRender.onDraw3D(
                      chart,
                      canvas,
                      over240Entity.drawStartAngle +
                          over240Entity.drawSweepAngle -
                          60,
                      60,
                      over240Entity,
                      false,
                      true,
                      false,
                      false);
                } else {
                  canvas.draw3DArcBottom(
                      center: Offset(centerX, centerY + height),
                      radius: over240Entity.drawRadius,
                      innerRadius: chart.innerRadius,
                      height: height,
                      flipAngle: flipAngle,
                      startAngle: over240Entity.drawStartAngle +
                          over240Entity.drawSweepAngle -
                          60,
                      sweepAngle: 60,
                      color: over240Entity.color,
                      offset: over240Entity.offset,
                      sliceSpace: sliceSpace);
                }
              }
            }
          }
        }
      }
    }

    // 这一步排序很重要，否则绘制会出现扇形直接被遮挡问题。
    // 此排序用于绘制第二部分
    dataList.sort(
      (o1, o2) {
        if (o1.drawRadius > o2.drawRadius) {
          return 1;
        } else if (o1.drawRadius == o2.drawRadius) {
          return 0;
        } else {
          return -1;
        }
      },
    );

    for (int j = 0; j < 2; j++) {
      for (int i = 0; i < dataList.length; i++) {
        var data = dataList[i];
        if (!data.isDraw || data.drawSweepAngle == 0) {
          continue;
        }

        if (data == touchData && highLightRender != null) {
          highLightRender.onDraw3D(chart, canvas, data.drawStartAngle,
              data.drawSweepAngle, data, j == 1, false, false, j == 0);
          continue;
        }

        if (j == 0) {
          canvas.draw3DArcShadow(
              center: Offset(centerX, centerY + height),
              radius: data.drawRadius,
              height: height,
              flipAngle: flipAngle,
              startAngle: data.drawStartAngle,
              sweepAngle: data.drawSweepAngle,
              color: data.color,
              offset: data.offset,
              sliceSpace: sliceSpace);
        } else {
          canvas.draw3DArcTop(
              center: Offset(centerX, centerY),
              radius: data.drawRadius,
              innerRadius: chart.innerRadius,
              flipAngle: flipAngle,
              startAngle: data.drawStartAngle,
              sweepAngle: data.drawSweepAngle,
              color: data.color,
              offset: data.offset,
              sliceSpace: sliceSpace);
        }
      }
    }
  }

  /// 获取给定起始角度及角度范围内获取最小Y轴坐标的角度
  double getMinYAngle(double startAngle, double sweep) {
    double start = startAngle.to360Range;
    double end = (startAngle + sweep).to360Range;

    if ((start <= 270 && start + sweep >= 270) ||
        (start > 270 && start + sweep >= 630)) {
      // 起始角度+角度范围覆盖了270°
      return 270;
    }

    if (start > 270) {
      // start 在270~360之间，不含270°
      // 结束角度在270~360及0~270之间，不含270°
      if (end > 270 || end < 180) {
        return start;
      } else if (270 - end > start - 270) {
        return start;
      } else {
        return end;
      }
    } else if (start >= 0 && start < 90) {
      // 结束角度在0~270之间，不含270°
      if (end >= 180) {
        return end;
      } else if (end < 90) {
        return start;
      } else if (end - 90 > 90 - start) {
        return end;
      } else {
        return start;
      }
    } else {
      // 结束角度在90~270之间，不含270°
      return end;
    }
  }

  @protected
  void drawBg(KqPieChart3DDelegate chart, Canvas canvas) {
    if (chart.baseMapColor.alpha == 0) {
      return;
    }

    double flipAngle = chart.drawFlipAngle;
    double height = chart.drawHeight;
    canvas.draw3DArcShadow(
        center: Offset(chart.size.centerX, chart.size.centerY + height),
        radius: chart.radius,
        height: height,
        flipAngle: flipAngle,
        startAngle: 0,
        sweepAngle: 360,
        color: chart.baseMapColor,
        drawTop: true);

    canvas.draw3DArcBottom(
        center: Offset(chart.size.centerX, chart.size.centerY + height),
        radius: chart.radius,
        innerRadius: chart.innerRadius,
        height: height,
        flipAngle: flipAngle,
        startAngle: 0,
        sweepAngle: 360,
        color: chart.baseMapColor);

    canvas.draw3DArcShadow(
        center: Offset(chart.size.centerX, chart.size.centerY + height),
        radius: chart.radius,
        height: height,
        flipAngle: flipAngle,
        startAngle: 0,
        sweepAngle: 360,
        color: chart.baseMapColor);

    canvas.draw3DArcTop(
        center: Offset(chart.size.centerX, chart.size.centerY),
        radius: chart.radius,
        innerRadius: chart.innerRadius,
        flipAngle: flipAngle,
        startAngle: 0,
        sweepAngle: 360,
        color: chart.baseMapColor);
  }
}

/// 绘制饼状图标签
class PieChart3DLabelRender with BasePieLabelRender<KqPieChart3DDelegate> {
  const PieChart3DLabelRender(
      {this.minShowLabelAngle, this.minShowLabelAngleByNear});

  /// 显示标签的饼图最小角度，小于此值的饼图不绘制标签，不设置则始终绘制。
  final double? minShowLabelAngle;

  /// 标签满足[minShowLabelAngle]条件且此值不为null，如果标签前后的饼图绘制角度大于此值，则依然绘制满足[minShowLabelAngle]条件的标签
  final double? minShowLabelAngleByNear;

  @override
  void onDraw(KqPieChart3DDelegate chart, Canvas canvas, double animProgress) {
    var dataList = chart.data;
    if (dataList.isNullOrEmpty) {
      return;
    }

    var labelPosition = chart.labelPosition;
    if (labelPosition is InSide) {
      drawInSideLabel(chart, canvas, labelPosition, dataList!);
    } else if (labelPosition is OutSide) {
      drawOutSideLabel(chart, canvas, labelPosition, dataList!);
    }
  }

  /// 当[LabelPosition]为[InSide]时，绘制显示在扇形内部的标签
  @protected
  void drawInSideLabel(KqPieChart3DDelegate chart, Canvas canvas,
      InSide labelPosition, List<PieChartEntity> dataList) {
    Offset center = chart.size.center(Offset.zero);
    var count = dataList.length;
    for (int i = 0; i < count; i++) {
      var value = dataList[i];
      if (!value.isDraw || !value.drawLabel) {
        continue;
      }

      if (minShowLabelAngle != null &&
          value.drawSweepAngle < minShowLabelAngle!) {
        if (minShowLabelAngleByNear == null) {
          continue;
        }

        var index = i + 1;
        if (index >= count) {
          index = 0;
        }
        if (dataList[index].drawSweepAngle < minShowLabelAngleByNear!) {
          continue;
        }

        index = i - 1;
        if (index < 0) {
          index = count - 1;
        }

        if (dataList[index].drawSweepAngle < minShowLabelAngleByNear!) {
          continue;
        }
      }

      double radius2 = (value.drawRadius +
              sqrt(pow(value.offset.dx, 2) + pow(value.offset.dy, 2))) *
          labelPosition.offsetCenterPercent;
      double radiusShort = radius2 * cos(chart.drawFlipAngle.toRadian);

      double startAngle;
      double sweepAngle;
      if (value.drawSweepAngle == 0) {
        // 使用PathMeasure测量路径坐标点时，如果path长度为0，则坐标将为0，0点
        // 因此将开始角度往后偏移10°，让结束点在开始位置
        startAngle = value.drawStartAngle - 10;
        sweepAngle = 10;
      } else {
        startAngle = value.drawStartAngle;
        sweepAngle = value.drawSweepAngle / 2;
      }

      // 此时圆心为圆柱顶部椭圆圆心
      Path path = Path();
      path.arcTo(
          Rect.fromLTRB(center.dx - radius2, center.dy - radiusShort,
              center.dx + radius2, center.dy + radiusShort),
          startAngle.toRadian,
          sweepAngle.toRadian,
          false);
      var computeMetric = path.computeMetric();
      if (computeMetric == null || computeMetric.length == 0) {
        continue;
      }

      var tangentForOffset =
          computeMetric.getTangentForOffset(computeMetric.length);
      if (tangentForOffset == null) {
        continue;
      }

      double x1 = tangentForOffset.position.dx;
      double y1 = tangentForOffset.position.dy;
      String? label = value.label;

      TextPainter? textPaint;
      TextPainter? textPaint2;
      if (label.isNotNullOrEmpty) {
        textPaint = TextPainter(
            text: TextSpan(
                text: label,
                style: TextStyle(
                    color: value.labelColor, fontSize: value.labelSize)),
            maxLines: 1,
            textAlign: TextAlign.center,
            textDirection: TextDirection.ltr);
        textPaint.layout();
      }

      String? secondLabel = value.secondLabel;
      if (secondLabel.isNotNullOrEmpty) {
        textPaint2 = TextPainter(
            text: TextSpan(
                text: secondLabel,
                style: TextStyle(
                    color: value.secondLabelColor,
                    fontSize: value.secondLabelSize)),
            maxLines: 1,
            textAlign: TextAlign.center,
            textDirection: TextDirection.ltr);
        textPaint2.layout();
      }

      double labelHeight = textPaint?.height ?? 0;
      double secondLabelHeight = textPaint2?.height ?? 0;
      if (textPaint != null) {
        double height = labelHeight + secondLabelHeight;
        textPaint.paint(
            canvas, Offset(x1 - textPaint.width / 2, y1 - height / 2));
      }

      if (textPaint2 != null) {
        double height = labelHeight + secondLabelHeight;
        textPaint2.paint(canvas,
            Offset(x1 - textPaint2.width / 2, y1 - height / 2 + labelHeight));
      }
    }
  }

  /// 当[LabelPosition]为[OutSide]时，绘制显示在扇形外部的标签
  @protected
  void drawOutSideLabel(KqPieChart3DDelegate chart, Canvas canvas,
      OutSide labelPosition, List<PieChartEntity> dataList) {
    Offset center = chart.size.center(Offset.zero);
    var count = dataList.length;
    for (int i = 0; i < count; i++) {
      var value = dataList[i];
      if (!value.isDraw || !value.drawLabel) {
        continue;
      }

      if (minShowLabelAngle != null &&
          value.drawSweepAngle < minShowLabelAngle!) {
        if (minShowLabelAngleByNear == null) {
          continue;
        }

        var index = i + 1;
        if (index >= count) {
          index = 0;
        }
        if (dataList[index].drawSweepAngle < minShowLabelAngleByNear!) {
          continue;
        }

        index = i - 1;
        if (index < 0) {
          index = count - 1;
        }

        if (dataList[index].drawSweepAngle < minShowLabelAngleByNear!) {
          continue;
        }
      }

      IndicatorLine indicatorLine = labelPosition.lineFactory.call(value);
      double centerAngle =
          (value.drawStartAngle + value.drawSweepAngle / 2).to360Range;
      double centerRadian = centerAngle.toRadian;
      double flipAngle = chart.drawFlipAngle;
      double height = chart.drawHeight;
      double radius2 = value.drawRadius +
          sqrt(pow(value.offset.dx, 2) + pow(value.offset.dy, 2)) +
          indicatorLine.offset;
      var radiusShort = radius2 * cos(flipAngle.toRadian);

      double startAngle;
      double sweepAngle;
      if (value.drawSweepAngle == 0) {
        // 使用PathMeasure测量路径坐标点时，如果path长度为0，则坐标将为0，0点
        // 因此将开始角度往后偏移10°，让结束点在开始位置
        startAngle = value.drawStartAngle - 10;
        sweepAngle = 10;
      } else {
        startAngle = value.drawStartAngle;
        sweepAngle = value.drawSweepAngle / 2;
      }

      Path path = Path();
      if (indicatorLine.drawStartCircle && indicatorLine.circleRadius > 0) {
        // 绘制指示线起始端处的圆点
        double radius = radius2 + indicatorLine.circleRadius;
        double radiusShort2 = radius * cos(flipAngle.toRadian);

        if (centerAngle > 180 && centerAngle < 360) {
          // 此时圆心为圆柱顶部椭圆圆心
          path.arcTo(
              Rect.fromLTRB(center.dx - radius, center.dy - radiusShort2,
                  center.dx + radius, center.dy + radiusShort2),
              startAngle.toRadian,
              sweepAngle.toRadian,
              false);
        } else {
          // 此时圆心为圆柱底部椭圆圆心
          path.arcTo(
              Rect.fromLTRB(
                  center.dx - radius,
                  center.dy - radiusShort2 + height,
                  center.dx + radius,
                  center.dy + radiusShort2 + height),
              startAngle.toRadian,
              sweepAngle.toRadian,
              false);
        }

        var computeMetric = path.computeMetric();
        if (computeMetric != null && computeMetric.length > 0) {
          var tangentForOffset =
              computeMetric.getTangentForOffset(computeMetric.length);
          if (tangentForOffset != null) {
            Paint paint = Paint()
              ..style = PaintingStyle.fill
              ..color = indicatorLine.circleColor;
            canvas.drawCircle(
                Offset(
                    tangentForOffset.position.dx, tangentForOffset.position.dy),
                indicatorLine.circleRadius,
                paint);
            radius2 += indicatorLine.circleRadius * 2;
            radiusShort = radius2 * cos(flipAngle.toRadian);
          }
        }
      }

      path.reset();
      if (centerAngle > 180 && centerAngle < 360) {
        // 此时圆心为圆柱顶部椭圆圆心
        path.arcTo(
            Rect.fromLTRB(center.dx - radius2, center.dy - radiusShort,
                center.dx + radius2, center.dy + radiusShort),
            startAngle.toRadian,
            sweepAngle.toRadian,
            false);
      } else {
        // 此时圆心为圆柱底部椭圆圆心
        path.arcTo(
            Rect.fromLTRB(center.dx - radius2, center.dy - radiusShort + height,
                center.dx + radius2, center.dy + radiusShort + height),
            startAngle.toRadian,
            sweepAngle.toRadian,
            false);
      }

      var computeMetric = path.computeMetric();
      if (computeMetric == null || computeMetric.length == 0) {
        continue;
      }
      var tangentForOffset =
          computeMetric.getTangentForOffset(computeMetric.length);
      if (tangentForOffset == null) {
        continue;
      }

      // 指示线第一段起始X坐标
      double x1 = tangentForOffset.position.dx;
      // 指示线第一段起始Y坐标
      double y1 = tangentForOffset.position.dy;

      // 标签宽度
      double labelWidth;
      double labelHeight;
      TextPainter? labelPainter;
      if (value.label.isNullOrEmpty) {
        labelWidth = 0;
        labelHeight = 0;
      } else {
        labelPainter = TextPainter(
          text: TextSpan(
              text: value.label,
              style: TextStyle(
                  color: value.labelColor, fontSize: value.labelSize)),
          maxLines: 1,
          textAlign: TextAlign.center,
          textDirection: TextDirection.ltr,
        )..layout();

        labelWidth = labelPainter.width;
        labelHeight = labelPainter.height;
      }

      // 第二个标签宽度
      double secondLabelWidth;
      double secondLabelHeight;
      TextPainter? secondLabelPainter;
      if (value.secondLabel.isNullOrEmpty ||
          labelPosition.labelGravity == LabelGravity.end) {
        secondLabelWidth = 0;
        secondLabelHeight = 0;
      } else {
        secondLabelPainter = TextPainter(
          text: TextSpan(
              text: value.secondLabel,
              style: TextStyle(
                  color: value.secondLabelColor,
                  fontSize: value.secondLabelSize)),
          maxLines: 1,
          textAlign: TextAlign.center,
          textDirection: TextDirection.ltr,
        )..layout();

        secondLabelWidth = secondLabelPainter.width;
        secondLabelHeight = secondLabelPainter.height;
      }

      // 两个标签中的最大值
      var maxWidth = max(labelWidth, secondLabelWidth);
      if (maxWidth < indicatorLine.minLength) {
        maxWidth = indicatorLine.minLength;
      } else if (indicatorLine.maxLength != null &&
          maxWidth > indicatorLine.maxLength!) {
        maxWidth = indicatorLine.maxLength!;
      }

      // 为了确保文本不会与饼状图重叠，计算在饼状图半径基础上的补充半径
      double addedRadius;
      switch (labelPosition.labelGravity) {
        case LabelGravity.end:
          addedRadius = labelHeight / 2;
          break;
        case LabelGravity.top:
          addedRadius = y1 < center.dy ? secondLabelHeight : labelHeight;
          break;
        default:
          addedRadius = y1 < center.dy ? labelHeight : secondLabelHeight;
          break;
      }

      double minLength = 6;
      // 指示线第一段最小长度为 6
      double minOneLine = addedRadius * sin(centerRadian).abs();
      if (minOneLine < minLength) {
        minOneLine = minLength;
      }

      // 标签水平方向与指示线间距
      double labelHorizontalOffset = minLength;
      double x2 = x1 + minOneLine * cos(centerRadian);
      double y2 = y1 + minOneLine * sin(centerRadian);
      double x3 = x2;
      if (x2 >= center.dx) {
        if (labelPosition.labelGravity == LabelGravity.end) {
          // 10px为绘制在指示线尾部时，第二根线条默认长度
          x3 += minLength;
        } else {
          x3 += labelHorizontalOffset + maxWidth;
        }
      } else if (labelPosition.labelGravity == LabelGravity.end) {
        x3 += -minLength;
      } else {
        x3 += -labelHorizontalOffset - maxWidth;
      }

      Path tempPath = Path();
      tempPath.moveTo(x1, y1);
      tempPath.lineTo(x2, y2);
      tempPath.lineTo(x3, y2);
      canvas.drawPath(
          tempPath,
          Paint()
            ..style = PaintingStyle.stroke
            ..strokeWidth = indicatorLine.width
            ..color = indicatorLine.color);

      if (labelPainter != null) {
        switch (labelPosition.labelGravity) {
          case LabelGravity.end:
            Offset offset;
            if (x3 - x2 > 0) {
              offset = Offset(x2 + labelHorizontalOffset, y2 - labelHeight / 2);
            } else {
              offset = Offset(
                  x2 - maxWidth - labelHorizontalOffset, y2 - labelHeight / 2);
            }
            labelPainter.paint(canvas, offset);
            break;
          case LabelGravity.top:
            Offset offset;
            if (x3 - x2 > 0) {
              offset = Offset(
                  x2 + labelHorizontalOffset + (maxWidth - labelWidth),
                  y2 - labelHeight - indicatorLine.width);
            } else {
              offset = Offset(x2 - maxWidth - labelHorizontalOffset,
                  y2 - labelHeight - indicatorLine.width);
            }
            labelPainter.paint(canvas, offset);
            break;
          default:
            Offset offset;
            if (x3 - x2 > 0) {
              offset = Offset(
                  x2 + labelHorizontalOffset + (maxWidth - labelWidth),
                  y2 + indicatorLine.width);
            } else {
              offset = Offset(x2 - maxWidth - labelHorizontalOffset,
                  y2 + indicatorLine.width);
            }
            labelPainter.paint(canvas, offset);
            break;
        }

        if (secondLabelPainter != null) {
          switch (labelPosition.labelGravity) {
            case LabelGravity.bottom:
              Offset offset;
              if (x3 - x2 > 0) {
                offset = Offset(
                    x2 + labelHorizontalOffset + (maxWidth - secondLabelWidth),
                    y2 - secondLabelHeight - indicatorLine.width);
              } else {
                offset = Offset(x2 - maxWidth - labelHorizontalOffset,
                    y2 - secondLabelHeight - indicatorLine.width);
              }
              secondLabelPainter.paint(canvas, offset);
              break;
            case LabelGravity.top:
              Offset offset;
              if (x3 - x2 > 0) {
                offset = Offset(
                    x2 + labelHorizontalOffset + (maxWidth - secondLabelWidth),
                    y2 + indicatorLine.width);
              } else {
                offset = Offset(x2 - maxWidth - labelHorizontalOffset,
                    y2 + indicatorLine.width);
              }
              secondLabelPainter.paint(canvas, offset);
              break;
            default:
              break;
          }
        }
      }
    }
  }
}

/// [KqPieChart3DDelegate]手势处理
class PieChart3DGestureHandler
    with BaseChartGestureHandlerMixin<KqPieChart3DDelegate> {

  /// 构建手势处理器
  ///
  /// [touchData]为当前选中数据，主动赋值相当于模拟用户点击
  PieChart3DGestureHandler(
      {this.tapEnable = true,
      this.dragEnable = true,
      this.rotationEnable = true,
      this.onTap,
      this.flipEnable = true,
      this.minFlipAngle = 0,
      this.maxFlipAngle = 80,
      PieChartEntity? touchData})
      : assert(maxFlipAngle >= 0 && maxFlipAngle <= 90),
        assert(minFlipAngle >= 0 &&
            minFlipAngle <= 90 &&
            minFlipAngle <= maxFlipAngle) {
    _touchData = touchData;
  }

  @override
  final bool tapEnable;

  @override
  final bool dragEnable;

  /// 是否支持旋转,此选项生效的前提的是[dragEnable]置为true
  final bool rotationEnable;

  /// 是否可以上下翻转,此选项生效的前提的是[dragEnable]置为true
  final bool flipEnable;

  /// 最大翻转角度
  final double maxFlipAngle;

  /// 最小翻转角度
  final double minFlipAngle;

  /// 点击监听，回调参数为空说明点击了非特定数据区域，不为空则为某个数据的绘制区域
  final PieChartTapCallback? onTap;

  double _rotation = 0;
  double? _firstMoveAngle;
  PieChartEntity? _touchData;
  PieChartEntity? _oldTouchData;
  KqPieChart3DDelegate? _chart;
  AnimationController? _animationController;

  /// 旋转方向，顺时针还是逆时针,最多取样5份
  List<int> _rotationDirection = [];
  double _flipAngle = 0;
  bool _isFlip = false;
  bool _isMove = false;

  @override
  void attachChart(KqPieChart3DDelegate delegate) {
    _animationController?.dispose();
    _chart = delegate;
  }

  @override
  void dispose() {
    _animationController?.dispose();
    _chart = null;
  }

  @override
  void onTapDown(DragDownDetails details) {
    if (_chart == null ||
        (!tapEnable && (!dragEnable || (!rotationEnable && !flipEnable)))) {
      return;
    }

    if (_animationController != null && _animationController!.isAnimating) {
      _animationController?.stop(canceled: true);
    }

    _isFlip = false;
    _isMove = false;
    _rotationDirection.clear();
    _firstMoveAngle = null;
    _oldTouchData = findClickData(details);
  }

  @override
  void onTapUp(DragDownDetails details) {
    if (_chart == null || !tapEnable) {
      return;
    }

    // 单次点击
    if (_touchData != _oldTouchData) {
      _touchData = _oldTouchData;
      _chart?.update();
      onTap?.call(_touchData);
    } else if (touchData != null) {
      _touchData = null;
      _chart?.update();
      onTap?.call(_oldTouchData);
    } else {
      onTap?.call(null);
    }
  }

  @override
  void onDragStart(DragStartDetails details) {}

  @override
  void onDragUpdate(DragUpdateDetails details) {
    var chart = _chart;
    if (chart == null) {
      return;
    }

    if (flipEnable && !_isMove) {
      if (_isFlip || details.delta.dx.abs() < details.delta.dy.abs()) {
        Offset center = chart.size.center(Offset.zero);
        double width = chart.radius * 0.5;
        if (_isFlip ||
            details.localPosition.dx > center.dx - width &&
                details.localPosition.dx < center.dx + width) {
          _isFlip = true;
          var newFlipAngle = flipAngle - details.delta.dy / 5;
          if (newFlipAngle + chart.flipAngle > maxFlipAngle) {
            newFlipAngle = maxFlipAngle - chart.flipAngle;
          } else if (newFlipAngle + chart.flipAngle < minFlipAngle) {
            newFlipAngle = minFlipAngle - chart.flipAngle;
          }

          if (newFlipAngle != flipAngle) {
            _flipAngle = newFlipAngle;
            chart.update();
          }
          return;
        }
      }
    }

    if (rotationEnable) {
      double angle =
          getAngleForPoint(details.localPosition.dx, details.localPosition.dy);
      if (_firstMoveAngle == null) {
        _firstMoveAngle = angle;
      } else {
        var sweepAngle = angle - (_firstMoveAngle ?? 0);
        if (sweepAngle != 0) {
          var direction = sweepAngle > 0 ? 1 : -1;
          if (_rotationDirection.length >= 5) {
            _rotationDirection.removeAt(0);
          }
          _rotationDirection.add(direction);
        }

        _rotation += sweepAngle;
        _rotation = _rotation.to360Range;
        _firstMoveAngle = angle;
        _chart?.update();
      }
      _isMove = true;
    } else {
      _isMove = false;
    }
  }

  @override
  void onDragEnd(DragEndDetails details) {
    if (_chart == null || !dragEnable || !rotationEnable || _isFlip) {
      return;
    }

    flingRotation(details);
  }

  /// 执行手势旋转动画
  @protected
  void flingRotation(DragEndDetails details) {
    var velocity = details.velocity.pixelsPerSecond;
    if (velocity.dx.abs() <= 50 && velocity.dy.abs() <= 50) {
      return;
    }

    KqPieChart3DDelegate? chart = _chart;
    if (chart == null) {
      return;
    }

    var state = chart.chartState;
    if (state == null) {
      return;
    }

    var direction = _rotationDirection;
    if (direction.isEmpty) {
      return;
    }

    double angleStep;
    double maxAngle = 8;
    int duration;
    if (velocity.dx.abs() > velocity.dy.abs()) {
      duration = velocity.dx.abs().toInt();
    } else {
      duration = velocity.dy.abs().toInt();
    }

    var clockwiseCount = 0;
    var counterclockwiseCount = 0;
    for (var value in _rotationDirection) {
      if (value > 0) {
        clockwiseCount++;
      } else {
        counterclockwiseCount++;
      }
    }

    angleStep = maxAngle * (clockwiseCount > counterclockwiseCount ? 1 : -1);

    if (_animationController?.isAnimating == true) {
      _animationController?.stop(canceled: true);
    }

    _animationController = AnimationController(
        vsync: state.vsync, duration: Duration(milliseconds: duration));
    var curvedAnimation = CurvedAnimation(
        parent: _animationController!, curve: Curves.decelerate);
    curvedAnimation.addListener(() {
      _rotation += angleStep * (1 - _animationController!.value);
      _rotation = rotation.to360Range;
      chart.update();
    });
    _animationController?.forward();
  }

  /// 查找点击位置对应的饼状图数据
  @protected
  PieChartEntity? findClickData(DragDownDetails details) {
    KqPieChart3DDelegate? chart = _chart;
    if (chart == null) {
      return null;
    }

    var dataList = chart.data;
    if (dataList == null || dataList.isEmpty) {
      return null;
    }

    double angleForPoint =
        getAngleForPoint(details.localPosition.dx, details.localPosition.dy);
    PieChartEntity? entity;
    Offset center = chart.size.center(Offset.zero);
    for (int i = 0; i < dataList.length; i++) {
      var data = dataList[i];
      double radius = data.drawRadius +
          sqrt(pow(data.offset.dx, 2) + pow(data.offset.dy, 2));
      double radiusShort = radius * cos(chart.drawFlipAngle.toRadian);

      if (details.localPosition.dx < center.dx - radius ||
          details.localPosition.dx > center.dx + radius) {
        continue;
      }

      if (details.localPosition.dy < center.dy - radiusShort ||
          details.localPosition.dy >
              center.dy + radiusShort + chart.drawHeight) {
        continue;
      }

      if (chart.innerRadius > 0) {
        double innerRadius = chart.innerRadius +
            sqrt(pow(data.offset.dx, 2) + pow(data.offset.dy, 2));
        double innerRadiusShort =
            innerRadius * cos(chart.drawFlipAngle.toRadian);
        double tx = details.localPosition.dx - center.dx;
        double ty = details.localPosition.dy - center.dy;
        double length = sqrt(tx * tx + ty * ty);
        if (length < innerRadiusShort - chart.drawHeight) {
          continue;
        }
      }

      var startAngle = data.drawStartAngle.to360Range;
      var endAngle = (startAngle + data.drawSweepAngle).to360Range;

      // 此时圆心为圆柱顶部椭圆圆心
      Path path = Path();
      path.arcTo(
          Rect.fromLTRB(center.dx - radius, center.dy - radiusShort,
              center.dx + radius, center.dy + radiusShort),
          startAngle.toRadian,
          data.drawSweepAngle.toRadian,
          false);
      var computeMetric = path.computeMetric();
      if (computeMetric == null || computeMetric.length == 0) {
        continue;
      }

      if (((startAngle >= 0 && startAngle <= 180) ||
              (endAngle >= 0 && endAngle <= 180)) &&
          details.localPosition.dy > center.dy + radiusShort) {
        // 点击了3D饼状图上下两个椭圆中间区域
        double startX;
        double endX;
        if (startAngle <= 180) {
          var tangentForOffset = computeMetric.getTangentForOffset(0);
          if (tangentForOffset == null) {
            continue;
          }
          endX = tangentForOffset.position.dx;

          if (endAngle >= 180) {
            startX = center.dx - radius;
          } else {
            tangentForOffset =
                computeMetric.getTangentForOffset(computeMetric.length);
            if (tangentForOffset == null) {
              continue;
            }
            startX = tangentForOffset.position.dx;
          }
        } else {
          endX = center.dx + radius;
          var tangentForOffset =
              computeMetric.getTangentForOffset(computeMetric.length);
          if (tangentForOffset == null) {
            continue;
          }
          startX = tangentForOffset.position.dx;
        }

        if (details.localPosition.dx >= startX &&
            details.localPosition.dx <= endX) {
          entity = data;
          break;
        }
      } else {
        var tangentForOffset = computeMetric.getTangentForOffset(0);
        if (tangentForOffset == null) {
          continue;
        }

        startAngle = getAngleForPoint(
            tangentForOffset.position.dx, tangentForOffset.position.dy);
        tangentForOffset =
            computeMetric.getTangentForOffset(computeMetric.length);
        if (tangentForOffset == null) {
          continue;
        }
        endAngle = getAngleForPoint(
            tangentForOffset.position.dx, tangentForOffset.position.dy);

        if (startAngle > endAngle) {
          if (angleForPoint >= startAngle && angleForPoint < 360) {
            entity = data;
            break;
          } else if (angleForPoint >= 0 && angleForPoint <= endAngle) {
            entity = data;
            break;
          }
        } else if (angleForPoint >= startAngle && angleForPoint <= endAngle) {
          entity = data;
          break;
        }
      }
    }

    return entity;
  }

  /// 获取触摸点相对于圆心的旋转角度
  @protected
  double getAngleForPoint(double x, double y) {
    var offset = _chart?.size.center(Offset.zero);
    if (offset == null) {
      return 0;
    }

    double tx = x - offset.dx;
    double ty = y - offset.dy;
    double length = sqrt(tx * tx + ty * ty);
    double r = acos(tx / length);
    var angle = r.toDegrees;
    if (y < offset.dy) angle = 360 - angle;
    return angle % 360;
  }

  /// 获取手势旋转角度
  double get rotation => _rotation;

  /// 返回点击区域对应的饼状图数据
  PieChartEntity? get touchData => _touchData;

  /// 翻转角度
  double get flipAngle => _flipAngle;
}

/// 3D饼状图高亮数据绘制器
class PieChart3DHighLightRender {
  const PieChart3DHighLightRender(
      {this.colorBuilder, this.addedRadius = 0, this.offsetRadius = 10})
      : assert(addedRadius >= 0),
        assert(offsetRadius >= 0);

  /// 高亮颜色构建器
  final Color Function(PieChartEntity)? colorBuilder;

  /// 高亮时半径增加的长度，中心点不发生偏移
  final double addedRadius;

  /// 高亮时中心点向外移动的半径长度，不增加绘制的半径长度
  final double offsetRadius;

  /// 绘制高亮数据，此方法会被调用多次
  ///
  /// [drawBottom] 是否是绘制底部扇形
  /// [drawTop] 是否是绘制顶部扇形
  /// [drawShadowBottom] 是否是绘制上半部分阴影(180°~360°范围)
  /// [drawShadowBottom] 是否是绘制下半部分阴影(0°~180°范围)
  void onDraw3D(
      KqPieChart3DDelegate chart,
      Canvas canvas,
      double drawStartAngle,
      double drawSweepAngle,
      PieChartEntity data,
      bool drawTop,
      bool drawBottom,
      bool drawShadowTop,
      bool drawShadowBottom) {
    Offset center = chart.size.center(Offset.zero);
    double radius = getRadius(chart, data);
    Offset offset = getOffset(data);
    Color color = colorBuilder?.call(data) ?? data.color;
    double flipAngle = chart.drawFlipAngle;
    double height = chart.drawHeight;
    if (drawShadowTop) {
      canvas.draw3DArcShadow(
          center: Offset(center.dx, center.dy + height),
          radius: radius,
          height: height,
          flipAngle: flipAngle,
          startAngle: drawStartAngle,
          sweepAngle: drawSweepAngle,
          color: color,
          offset: offset,
          sliceSpace: chart.sliceSpace,
          drawTop: true);
    }

    if (drawBottom) {
      canvas.draw3DArcBottom(
          center: Offset(center.dx, center.dy + height),
          radius: radius,
          innerRadius: chart.innerRadius,
          height: height,
          flipAngle: flipAngle,
          startAngle: drawStartAngle,
          sweepAngle: drawSweepAngle,
          color: color,
          offset: offset,
          sliceSpace: chart.sliceSpace);
    }

    if (drawShadowBottom) {
      canvas.draw3DArcShadow(
          center: Offset(center.dx, center.dy + height),
          radius: radius,
          height: height,
          flipAngle: flipAngle,
          startAngle: drawStartAngle,
          sweepAngle: drawSweepAngle,
          color: color,
          offset: offset,
          sliceSpace: chart.sliceSpace);
    }

    if (drawTop) {
      canvas.draw3DArcTop(
          center: center,
          radius: radius,
          innerRadius: chart.innerRadius,
          flipAngle: flipAngle,
          startAngle: drawStartAngle,
          sweepAngle: drawSweepAngle,
          color: color,
          offset: offset,
          sliceSpace: chart.sliceSpace);
    }
  }

  /// 返回高亮绘制内容的半径
  double getRadius(KqPieChart3DDelegate chart, PieChartEntity data) {
    return chart.radius + data.addedRadius + addedRadius;
  }

  /// 获取X轴，Y轴偏移值
  Offset getOffset(PieChartEntity data) {
    if (offsetRadius <= 0) {
      return Offset.zero;
    }

    double centerRadian =
        (data.drawStartAngle + data.drawSweepAngle / 2).toRadian;
    return Offset(
        offsetRadius * cos(centerRadian), offsetRadius * sin(centerRadian));
  }
}
